-- world_mgr.lua

require("core.Store")

local tinsert    = table.insert
local tsort      = table.sort
local sformat    = string.format
local lmkdir     = stdfs.mkdir
local lfilename  = stdfs.filename
local lcopy      = stdfs.copy
local rfile      = stdfs.remove
local ldir       = stdfs.dir
local extension  = stdfs.extension
local sgsub      = string.gsub

local GST_AUTO   = 0  -- 自动
local GST_QUICK  = 2  -- 快捷
local GST_STORY  = 3  -- 剧情

WorldMgr         = singleton()
local prop       = property(WorldMgr)

prop:accessor("account_file", nil)            -- 索引文件
prop:accessor("game_cache_path", "")
prop:accessor("game_db_path", "")
prop:accessor("game_log_path", "")
prop:accessor("cache_path", "")         -- 缓存路径
prop:accessor("user_cache_path", "")    -- 用户缓存路径
prop:accessor("user_db_file", "")       -- 用户索引路径
prop:accessor("user_db_cache_path", "") -- 用户db缓存路径
prop:accessor("user_log_cache_path", "")-- 用户日志缓存路径
prop:accessor("world_map", {})
prop:accessor("world_list", {})
prop:accessor("world_childs", {})
prop:accessor("user", "")
prop:accessor("host", "")
prop:accessor("open_id", "")

prop:accessor("online_time", 0)
prop:accessor("client_version", "-")
prop:accessor("client_version_id", 0)

function WorldMgr:__init()
    self.game_cache_path = os.getenv("USERPROFILE") .. "/AppData/LocalLow/Pathea Games/My Time at Evershine"
    self.game_db_path = self.game_cache_path .. "/remote/Saves/"
    self.game_log_path = self.game_cache_path .. "/logs"
    self.cache_path = os.getenv("USERPROFILE") .. "/AppData/LocalLow/Pathea Games/StoreEditor/cache"
    lmkdir(self.cache_path)
end

function WorldMgr:copyWorld(child)
    local name = sgsub(child.name, "%.db$", "")
    local source_path = child.path
    local target_path = sformat("%s/%s_copy.db", self.game_db_path, name)
    rfile(target_path)
    print(sformat("name:%s source_path:%s target_path:%s", name, source_path, target_path))
    local ok, err = lcopy(source_path, target_path)
    if not ok then
        print(sformat("[WorldMgr][copyWorld] fail err:%s (source:%s target:%s)", err, source_path, target_path))
        return false
    end
    print(sformat("[WorldMgr][copyWorld] success (source:%s target:%s)", source_path, target_path))
    return true
end

-- 初始化用户目录
function WorldMgr:initUserDir()
    lmkdir(self.game_db_path)
    lmkdir(self.user_cache_path)
    lmkdir(self.user_db_cache_path)
    lmkdir(self.user_log_cache_path)
end

function WorldMgr:loginRemote(host, port, callback)
    g_lan_mgr:bind_target(host, port)
    g_lan_mgr:send("login",{}, function(err, data)
        if err then
            print("[LanMgr][loginRemote] err="..err)
            callback(err)
            return
        end
        self.user = data.user
        self.host = data.host
        self.open_id = data.open_id
        -- 用户缓存路径
        self.user_cache_path = sformat("%s/%s", self.cache_path, self.user)
        -- 用户db缓存路径
        self.user_db_cache_path = sformat("%s/db", self.user_cache_path)
        -- 账号索引db路径
        self.user_db_file = sformat("%s/%s.db", self.user_db_cache_path, self.open_id)
        -- 用户日志缓存路径
        self.user_log_cache_path = sformat("%s/logs", self.user_cache_path)
        self:initUserDir()
        callback()
    end)
end

-- 通用排序规则（类型优先，同类型按时间降序）
function commonSortRule(a, b)
    -- 类型优先级：2(快捷) > 3(剧情) > 0(自动) > 1(手动)
    if a.store_type ~= b.store_type then
        -- 快捷类型最高优先级
        if a.store_type == GST_QUICK then return true end
        if b.store_type == GST_QUICK then return false end
        -- 剧情类型第二优先级
        if a.store_type == GST_STORY then return true end
        if b.store_type == GST_STORY then return false end
        -- 自动类型第三优先级
        if a.store_type == GST_AUTO then return true end
        if b.store_type == GST_AUTO then return false end
        -- 手动类型最低优先级
        return true
    end
    -- 同类型按时间降序
    return a.store_operate_time > b.store_operate_time
end

-- 找出数组中的最大时间值
function WorldMgr:findMaxTime(arr)
    local maxTime = -math.huge
    for _, item in ipairs(arr) do
        if item.store_operate_time > maxTime then
            maxTime = item.store_operate_time
        end
    end
    return maxTime
end

-- 世界排序
function WorldMgr:sortWorlds()
    self.world_list = {}
    for _, world in pairs(self.world_map) do
        tinsert(self.world_list, world)
    end
    -- 父级排序
    tsort(self.world_list, function(a, b)
        return a.operate_time > b.operate_time
    end)
    -- -- 子级排序
    for i, world in ipairs(self.world_list) do
        if i == 1 then
            local maxTime = self:findMaxTime(self.world_list[i].childs)
            tsort(self.world_list[i].childs, function(a, b)
                -- 首先检查是否是全局最大时间
                if a.store_operate_time == maxTime or b.store_operate_time == maxTime then
                    return a.store_operate_time == maxTime
                end
                -- 否则使用通用排序规则
                return commonSortRule(a, b)
            end)
        else
            tsort(world.childs, commonSortRule)
        end
    end
end

function WorldMgr:loadData()
    self:loadLocalWorlds()
end

function WorldMgr:upAccountGameVersion(child, version_id)
    local count = 0
    local fileName = lfilename(child.path)
    if not self.account_file then
        return count
    end
    local path = self.account_file
    if not g_store_mgr:load(self.account_file) then
        print("[WorldMgr][upAccountGameVersion] load file failed:", path)
        return count
    end
    local store = g_store_mgr:getCurStore()
    if not store then
        print("[WorldMgr][upAccountGameVersion] store file failed:", path)
        return count
    end
    local sheet = "storage_childs"
    local sheets = store.sheets[sheet]
    if not sheets then
        print("[WorldMgr][upAccountGameVersion] sheet_tab file failed:", sheet)
        return count
    end
    for id, val in pairs(sheets) do
        local childs = val.childs
        for key, item in pairs(childs) do
            if item.dbfile..".db" == fileName then
                sheets[id].childs[key].version_id = version_id
                store:put(id, val, sheet)
                count = count + 1
                print("[WorldMgr][upAccountGameVersion] success path:"..path.." key:"..key.." version_id:"..version_id)
            end
        end
    end
    local name = stdfs.filename(path)
    g_store_mgr:close(name)
    return count
end

function WorldMgr:loadLocalDb(dir, dbname)
    if not g_store_mgr:load(dbname) then
        print("[BrowseUI][loadLocalDb]load file failed:", dbname)
        return false
    end
    local master_player = nil
    local game_world = {}
    local world_storage = {}
    local world_utility = {}
    local story_mode = 3
    local player_attr = {}
    local name = lfilename(dbname)
    local store = g_store_mgr:get(name)
    local sheets = store:getTables()
    for _, info in pairs(sheets) do
        local sheet, values = table.unpack(info)
        if sheet == "game_world" then
            game_world = values[next(values)]
        elseif sheet == "world_utility" then
            world_utility = values[next(values)]
        elseif sheet == "world_storage" then
            world_storage = values[next(values)]
        elseif sheet == "player_attr" then
            player_attr = values
        elseif sheet == "account" then
            local acc_key = next(values)
            if acc_key then
                local account = values[acc_key]
                local online_time = account.online_time or 0
                if online_time > self.online_time then
                    self.online_time = online_time
                    self.client_version = account.client_version or ""
                    self.client_version_id = account.client_version_id or 0
                    self.account_file = dir.name
                    print("account client_version:"..self.client_version.." client_version_id:"..self.client_version_id)
                end
            end
        end
    end
    if not next(game_world) then
        print("[BrowseUI][getDbData]data file failed:", dbname)
        g_store_mgr:close(name)
        return false
    end
    for _, player in pairs(player_attr) do
        if player.player_id == game_world.master_id then
            master_player = player
        end
    end
    if next(world_utility) and world_utility.settings then
        story_mode = world_utility.settings[2] or 0
        if story_mode > 2 then
            story_mode = 3
        end
    end
    g_store_mgr:close(name)
    return true, game_world, world_storage, master_player, story_mode
end

function WorldMgr:loadLocalWorlds()
    self.world_map = {}
    self.world_list = {}
    self.world_childs = {}
    local child_id = 0
    local dirs = ldir(self.game_db_path)
    for _, dir in pairs(dirs) do
        if dir.type == "directory" or extension(dir.name) == ".db" then
            local u8name = dir.name
            local name = lfilename(u8name)
            local ok, game_world, world_storage, master_player, story_mode = self:loadLocalDb(dir, u8name)
            if not ok then
                print("[BrowseUI][initWorlds] getDbData dbname:", u8name)
                goto continue
            end
            local world_id = game_world.world_id
            if not self.world_map[world_id] then
                local world_name = game_world.world_name
                if not world_name or world_name == "" then
                    world_name = sformat("%s的世界", master_player.attrs[16]) or ""
                end
                self.world_map[world_id] = {
                    id = world_id,
                    world_name = world_name,
                    world_lvl = game_world.world_lvl,
                    world_time = game_world.world_time,
                    game_duration = game_world.game_times,
                    save_time = game_world.save_time,
                    create_time = game_world.create_time,
                    operate_time = 0,
                    master_nick = master_player.attrs[16],
                    childs = {},
                    types = {},
                    node = nil
                }
            end
            local child = {
                id = child_id,
                world_id = world_id,
                name = name,   -- 存档文件名称(带后缀)
                path = u8name, -- 存档文件路径
                path_utf8 = dir.name,
                dbname = sgsub(name, "%.db$", ""), -- 存档文件名称(不带后缀)
                store_name = world_storage.store_name or "",
                store_sub_name = world_storage.store_sub_name or "",
                store_type = world_storage.store_type or 0,
                store_thumb = world_storage.store_thumb or "",
                store_version_id = world_storage.store_version_id,
                store_channel_id = world_storage.store_channel_id,
                store_operate_time = world_storage.store_operate_time,
                story_mode = story_mode,
                node = nil,
                parent_node = nil,
            }
            if self.world_map[world_id].operate_time == 0 or child.store_operate_time > self.world_map[world_id].operate_time then
                self.world_map[world_id].operate_time = child.store_operate_time
                self.world_map[world_id].world_lvl = game_world.world_lvl
                self.world_map[world_id].create_time = game_world.create_time
                self.world_map[world_id].game_duration = game_world.game_times
            end
            tinsert(self.world_map[world_id].childs, child)
            self.world_childs[child_id] = child
            local num = self.world_map[world_id].types[child.store_type] or 0
            self.world_map[world_id].types[child.store_type] = num + 1
            child_id = child_id + 1
            :: continue ::
        end
    end
    self:sortWorlds()
end

function WorldMgr:loadRemoteWorlds(callback)
    self.world_map = {}
    self.world_list = {}
    self.world_childs = {}
    g_lan_mgr:send_no_anim("storage_list", {}, function(err, data)
        if err then
            print("[WorldMgr][loadRemoteWorlds] storage_list err:" .. err)
            callback(err)
            return
        end
        local storages = data.storages
        for _, world in pairs(storages or {}) do
            local world_id = world.wid
            local master_nick = world.master_nick
            local world_name = sformat("%s的世界", master_nick) or ""
            self.world_map[world_id] = {
                id = world_id,
                storage_id = world.id,
                world_name = world_name,
                world_lvl = 0,
                world_time = 0,
                game_duration = 0,
                save_time = 0,
                create_time = 0,
                operate_time = 0,
                master_nick = master_nick,
                childs = {},
                types = {},
                node = nil
            }
            local childs = world.childs
            for i, item in ipairs(childs) do
                local dbfile = item.dbfile..".db"
                local dbpath = sformat("%s/%s", self.user_db_cache_path, dbfile)
                local child = {
                    id = item.id,
                    storage_id = world.id,
                    world_id = world_id,
                    name = dbfile, -- 存档文件名称(带后缀)
                    path = dbpath, -- 存档文件路径
                    path_utf8 = dbpath,
                    dbname = sgsub(dbfile, "%.db$", ""), -- 存档文件名称(不带后缀)
                    store_name = "",
                    store_sub_name = item.name or "",
                    store_type = item.type or 0,
                    store_thumb = "",
                    store_version_id = item.version_id,
                    store_channel_id = item.channel_id,
                    store_operate_time = item.operate_time,
                    node = nil,
                    parent_node = nil,
                }
                if i == 1 then
                    self.world_map[world_id].world_lvl = item.world_lvl
                    self.world_map[world_id].create_time = item.create_time
                end
                if self.world_map[world_id].operate_time == 0 or child.store_operate_time > self.world_map[world_id].operate_time then
                    self.world_map[world_id].operate_time = child.store_operate_time
                    self.world_map[world_id].world_lvl = item.world_lvl
                    self.world_map[world_id].create_time = item.create_time
                    self.world_map[world_id].game_duration = item.game_duration
                end
                tinsert(self.world_map[world_id].childs, child)
                self.world_childs[child.id] = child
                local num = self.world_map[world_id].types[child.store_type] or 0
                self.world_map[world_id].types[child.store_type] = num + 1
            end
        end
        self:sortWorlds()
        callback(nil)
    end, nil, false)
end

function WorldMgr:queryRemoteThmb(world_id, child_id, callback)
    local world_child = g_world_mgr.world_childs[child_id]
    if not world_child then
        print("[WorldMgr][queryThmb] world_child err")
        callback("world_child err")
        return
    end
    if world_child.store_thumb ~= "" then
        callback(nil, { thumb = world_child.store_thumb })
        return
    end
    g_lan_mgr:send("storage_query", { id = world_id, child_id = child_id }, function(err, data)
        if err then
            print("[WorldMgr][queryThmb] storage_query err:" .. err)
            callback(err)
            return
        end
        world_child.store_thumb = data.thumb
        callback(nil, { thumb = data.thumb })
    end)
end

function WorldMgr:downRemoteDbFile(dbname, callback)
    local db_path = sformat("%s/%s.db", self.user_db_cache_path, dbname)
    -- 下载存档
    g_lan_mgr:down_file("storage_download", {dbname=dbname}, function(err, data)
        if err then
            print("[WorldMgr][storage_download] err="..err)
            callback(err)
            return
        end
        -- 先删除本地的
        rfile(db_path)
        local contents = data.contents
        g_lan_mgr:write_file(db_path, contents)
        callback(nil, {
            path = db_path
        })
    end)
end

-- 保存存档(dbname-不带后缀)
function WorldMgr:saveRemoteDb(dbname, sheet, key, value, callback)
    local data = {
        dbname = dbname,
        sheet = sheet,
        key = key,
        value = value
    }
    print("[WorldMgr][saveRemoteDb] data:"..json.encode(data))
    g_lan_mgr:send("save_store", data, function(err)
        if err then
            print("[WorldMgr][saveRemoteDb] save_store err:" .. err)
            callback(err)
            return
        end
        self:downRemoteDbFile(dbname, function(err1)
            if not err1 then
                callback(err1)
                return
            end
            callback()
        end)
    end)
end

-- 保存存档(dbname-不带后缀)
function WorldMgr:saveRemoteVersion(dbname, world_id, version, callback)
    local data = {
        dbname = dbname,
        world_id = tostring(world_id),
        version = version
    }
    print("[WorldMgr][saveRemoteVersion] data:"..json.encode(data))
    g_lan_mgr:send("save_version", data, function(err)
        if err then
            print("[WorldMgr][saveRemoteDb] save_store err:" .. err)
            callback(err)
            return
        end
        self:downRemoteDbFile(dbname, function(err1)
            if not err1 then
                callback(err1)
                return
            end
            callback()
        end)
    end)
end
g_world_mgr = WorldMgr()
